#define CONTINOUS_SPLINE

using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using System.Xml;

using Skeletons;

namespace Rigging
{
	public class SPLine : RiggingEntity
	{
		private Color m_clrPen = Color.Black;
		private Color m_clrFill = Color.Gray;
		private float m_fPenWidth = 1F;
		private int m_iActivePointIdx = -1; // 0,1,2,3,4,5... or -1 (none)
		private Point m_pntDragReference;
		private bool m_bCommitted = false;

		public SPLine(Skeletons.Bone parent, Color outlineClr, Color fillClr, float width)
			: base()
		{
			m_bone = parent;
			m_clrPen = outlineClr;
			m_clrFill = fillClr;
			m_fPenWidth = width;
			m_iActivePointIdx = -1;
			m_bCommitted = false;
		}

		public List<Point> Points
		{
			get { return m_points; }
			set { m_points = value; }
		}

		public override bool Selected
		{
			get { return m_selected; }
			set
			{
				m_selected = value;
				if (!m_selected)
					m_iActivePointIdx = -1;
			}
		}
		
		public void CommitPoints()
		{
			if(m_points.Count < 4)
				return;
			int expected = m_points.Count;
			expected -= 1;
			expected /= 3;
			expected *= 3;
			expected += 1;			
			if(expected < 4)
				expected = 4;

			while(m_points.Count > expected)
				m_points.RemoveAt(m_points.Count - 1);
			m_bCommitted = true;
		}

		public override bool IsUnder(Point p, int treshold)
		{
			int offset = 0;
			int count = m_points.Count;
			
			while(offset < count)
			{
				int remainingPoints = count - offset;
				Point pt1 = m_points[offset];
				Point pt2 = remainingPoints > 1 ? m_points[offset + 1] : pt1;
				Point pt3 = remainingPoints > 2 ? m_points[offset + 2] : pt2;
				Point pt4 = remainingPoints > 3 ? m_points[offset + 3] : pt3;

				if ((Math.Abs(pt1.X - p.X) < treshold) && (Math.Abs(pt1.Y - p.Y) < treshold))
				{
					m_iActivePointIdx = offset;
					return true;
				}
				if ((Math.Abs(pt2.X - p.X) < treshold) && (Math.Abs(pt2.Y - p.Y) < treshold))
				{
					m_iActivePointIdx = offset + 1;
					return true;
				}
				if ((Math.Abs(pt3.X - p.X) < treshold) && (Math.Abs(pt3.Y - p.Y) < treshold))
				{
					m_iActivePointIdx = offset + 2;
					return true;
				}
				if ((Math.Abs(pt4.X - p.X) < treshold) && (Math.Abs(pt4.Y - p.Y) < treshold))
				{
					m_iActivePointIdx = offset + 3;
					return true;
				}

				m_pntDragReference = p;
				m_iActivePointIdx = -1;


				for (float t = 0; t < 1.0; t += 0.01F)
				{
					float fW = 1 - t;
					float fA = fW * fW * fW;
					float fB = 3 * t * fW * fW;
					float fC = 3 * t * t * fW;
					float fD = t * t * t;
					float fX = fA * pt1.X + fB * pt2.X + fC * pt3.X + fD * pt4.X;

					if (Math.Abs(p.X - fX) > treshold)
						continue;

					float fY = fA * pt1.Y + fB * pt2.Y + fC * pt3.Y + fD * pt4.Y;

					if (Math.Abs(p.Y - fY) <= treshold)
						return true;
				}
				
				offset += 3;
			}
			
			return false;
		}

		public override bool ContainedByRect(Matrix mt, Rectangle r)
		{
			Point[] pts = m_points.ToArray();
			mt.TransformPoints(pts);
			foreach (Point p in pts)
			{
				if (!r.Contains(p))
					return false;
			}
			return true;
		}

		public void AddPoint(Point p)
		{
			m_points.Add(p);
#if CONTINOUS_SPLINE
			// points 4, 7, 10, 13, ... add an additional point
			int cnt = m_points.Count - 1;
			if(cnt < 3)
				return;
			if((cnt % 3) == 0)
			{
				Point prev = m_points[cnt - 1];
				int fX = p.X + (p.X - prev.X);
				int fY = p.Y + (p.Y - prev.Y);
				m_points.Add(new Point(fX,fY));
			}
#endif
		}

		public void MoveLastPoint(Point p)
		{
#if CONTINOUS_SPLINE
			// if attempting to move point 5, 8, 11, 14... move point 4, 7, 10... and points 3-5, 6-8, ...
			if (m_points.Count > 4)
			{
				if(((m_points.Count - 5) % 3) == 0)
				{
					int idx = m_points.Count - 2;
					int xOffs = p.X - m_points[idx].X;
					int yOffs = p.Y - m_points[idx].Y;
					m_points[idx] = p;
					Point prev = m_points[idx - 1];
					Point next = m_points[idx + 1];
					m_points[idx - 1] = new Point(prev.X + xOffs, prev.Y + yOffs);
					m_points[idx + 1] = new Point(next.X + xOffs, next.Y + yOffs);
					return;
				}
			}
#endif
			if (m_points.Count > 0)
				m_points.RemoveAt(m_points.Count - 1);
			m_points.Add(p);
		}

		public void MoveActivePoint(Point p)
		{
			switch (m_iActivePointIdx)
			{
				case -1:
					// move all points
					int xoffs = p.X - m_pntDragReference.X;
					int yoffs = p.Y - m_pntDragReference.Y;
					for (int i = 0; i < m_points.Count; i++)
					{
						Point pp = m_points[i];
						pp.X += xoffs;
						pp.Y += yoffs;
						m_points[i] = pp;
					}
					m_pntDragReference = p;
					break;
				default:
					if (m_points.Count < (m_iActivePointIdx + 1))
						return;

#if CONTINOUS_SPLINE
					// if attempting to move point 4, 7, 10...
					if ((m_points.Count > 3) && (m_iActivePointIdx > 3) && (m_iActivePointIdx < (m_points.Count - 1)))
					{
						if ((m_iActivePointIdx % 3) == 0)
						{
							int xOffs = p.X - m_points[m_iActivePointIdx].X;
							int yOffs = p.Y - m_points[m_iActivePointIdx].Y;
							m_points[m_iActivePointIdx] = p;
							Point prev = m_points[m_iActivePointIdx - 1];
							Point next = m_points[m_iActivePointIdx + 1];
							m_points[m_iActivePointIdx - 1] = new Point(prev.X + xOffs, prev.Y + yOffs);
							m_points[m_iActivePointIdx + 1] = new Point(next.X + xOffs, next.Y + yOffs);
							return;
						}
					}
					// if attempting to move point 3, 6, 9...
					if ((m_points.Count > 3) && (m_iActivePointIdx > 1) && (m_iActivePointIdx < (m_points.Count - 2)))
					{
						if (((m_iActivePointIdx+1) % 3) == 0)
						{
							Point middle = m_points[m_iActivePointIdx + 1];
							Point handle = p;
							int x = middle.X + (middle.X - handle.X);
							int y = middle.Y + (middle.Y - handle.Y);
							m_points[m_iActivePointIdx] = p;
							m_points[m_iActivePointIdx + 2] = new Point(x,y);
							return;
						}
					}
					// if attempting to move point 5, 8, 11...
					if ((m_points.Count > 3) && (m_iActivePointIdx > 3) && (m_iActivePointIdx < (m_points.Count - 2)))
					{
						if (((m_iActivePointIdx - 1) % 3) == 0)
						{
							Point middle = m_points[m_iActivePointIdx - 1];
							Point handle = p;
							int x = middle.X + (middle.X - handle.X);
							int y = middle.Y + (middle.Y - handle.Y);
							m_points[m_iActivePointIdx] = p;
							m_points[m_iActivePointIdx - 2] = new Point(x, y);
							return;
						}
					}		
#endif

					m_points[m_iActivePointIdx] = p;
					break;
			}
		}

		public override void LoadPoints(Point[] pts)
		{
			m_points = new List<Point>(pts);
			m_bCommitted = true;
		}

		public bool IsEmpty()
		{
			return m_points.Count < 1;
		}

		public bool IsComplete()
		{
			return m_points.Count >= 4;
		}

		public override void Repaint(Graphics gr)
		{
			if (m_points.Count < 1)
				return; // nothing to paint

			int offset = 0;
			int count = m_points.Count;

			
			if(m_bCommitted)
			{
				// first the fill
				GraphicsPath path = new GraphicsPath();

				while(offset < count)
				{
					int remainingPoints = count - offset;
				
					Point pt1 = m_points[offset];
					Point pt2 = remainingPoints > 1 ? m_points[offset + 1] : pt1;
					Point pt3 = remainingPoints > 2 ? m_points[offset + 2] : pt2;
					Point pt4 = remainingPoints > 3 ? m_points[offset + 3] : pt3;

					offset += 3;

					// draw filling selected color
					path.AddBezier(pt1, pt2, pt3, pt4);
				}

				gr.FillPath(new Pen(m_clrFill).Brush, path);

				offset = 0;
			}
			
			
			while(offset < count)
			{
				int remainingPoints = count - offset;
			
				Point pt1 = m_points[offset];
				Point pt2 = remainingPoints > 1 ? m_points[offset + 1] : pt1;
				Point pt3 = remainingPoints > 2 ? m_points[offset + 2] : pt2;
				Point pt4 = remainingPoints > 3 ? m_points[offset + 3] : pt3;

				offset += 3;

				// then overlay the outline            
				Pen penCurrent = new Pen(m_outlineColor.Color, m_fPenWidth);  // changed this to use outline color (black)

				gr.DrawBezier(penCurrent, pt1, pt2, pt3, pt4);

				if (m_selected)
				{
					// draw control lines
					penCurrent.Width = 1.0F;
					penCurrent.DashStyle = DashStyle.DashDot;

					gr.DrawLine(penCurrent, pt1, pt2);
					gr.DrawLine(penCurrent, pt3, pt4);

					// draw points
					foreach (Point p in m_points)
					{
						PaintHandle(gr, p);
					}
				}
			}
		}

		public override string ToXmlString(string indent)
		{
			string color = m_clrFill.ToString(); //.Replace("Color [","").Replace("]","");
			string ret = indent + "<SKIN type='SPLine' boneId='" + Bone.Id + "' color='" + color + "' penWidth='" + m_fPenWidth + "' >\n";
			for (int i = 0; i < m_points.Count; i++)
				ret += indent + "\t<POINT index='" + i + "' X='" + m_points[i].X + "' Y='" + m_points[i].Y + "'/>\n";
			ret += indent + "</SKIN>\n";

			return ret;
		}
	}

}
